/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

package com.huawei.housekeeper.service.impl;

import com.huawei.housekeeper.common.constant.StatusConstant;
import com.huawei.housekeeper.common.entity.SetPropertyDto;
import com.huawei.housekeeper.common.enums.ErrorCode;
import com.huawei.housekeeper.common.exception.Assert;
import com.huawei.housekeeper.common.model.TenantRouteProperties;
import com.huawei.housekeeper.common.result.ListRes;
import com.huawei.housekeeper.common.utils.CommonUtil;
import com.huawei.housekeeper.config.datasource.DataSourceConfiguration;
import com.huawei.housekeeper.controller.convert.FlywayConvert;
import com.huawei.housekeeper.controller.convert.TenantConvert;
import com.huawei.housekeeper.controller.request.CreateTenantDto;
import com.huawei.housekeeper.controller.request.PageQueryTenantDto;
import com.huawei.housekeeper.controller.request.UpdateTenantDto;
import com.huawei.housekeeper.controller.response.GetMigrationInformationVo;
import com.huawei.housekeeper.controller.response.GetTenantDetailVo;
import com.huawei.housekeeper.dao.entities.Tenant;
import com.huawei.housekeeper.dao.mapper.PropertyMapper;
import com.huawei.housekeeper.dao.mapper.TenantMapper;
import com.huawei.housekeeper.service.ConfigService;
import com.huawei.housekeeper.service.TenantService;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import lombok.extern.log4j.Log4j2;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 租户注册业务层
 *
 * @author lWX1128557
 * @since 2022-03-02
 */
@Service
@Log4j2
public class TenantServiceImpl extends ServiceImpl<TenantMapper, Tenant> implements TenantService {
    @Autowired
    private TenantMapper tenantMapper;

    @Autowired
    private ConfigService configService;

    @Autowired
    private DataSourceConfiguration dataSourceConfiguration;

    @Autowired
    private TenantRouteProperties tenantRouteProperties;

    @Autowired
    private PropertyMapper propertyMapper;

    /**
     * 租户注册
     *
     * @param createTenantDto 租户注册Dto
     * @return Integer 注册结果
     */
    @Override
    public String saveTenant(CreateTenantDto createTenantDto) {

        // 查询所有租户信息
        List<Tenant> tenants = getTenants();

        // 判断是否有域名、名字、信用码重复，重复则抛出异常
        tenants.stream()
            .forEach(t -> Assert.isFalse(StringUtils.equalsIgnoreCase(t.getDomain(), createTenantDto.getDomain())
                || StringUtils.equalsIgnoreCase(t.getName(), createTenantDto.getName()) || (createTenantDto.getNumber()
                .equals(t.getNumber())), ErrorCode.TENANT_REPEAT.getCode(), ErrorCode.TENANT_REPEAT.getMessage()));
        Tenant tenant = TenantConvert.INSTANCE.toTenant(createTenantDto);
        tenant.setTenantId(CommonUtil.getUUID());
        tenantMapper.insert(tenant);
        return tenant.getTenantId();
    }

    /**
     * 查询租户详情
     *
     * @param tenantNumber 租户企业信用码
     * @return GetTenantDetailVo 租户详情
     */
    @Override
    public GetTenantDetailVo getTenantDetail(String tenantNumber) {
        LambdaQueryWrapper<Tenant> select = Wrappers.lambdaQuery();
        Tenant tenant = tenantMapper.selectOne(select.eq(Tenant::getNumber, tenantNumber).select(Tenant::getStatus));
        return TenantConvert.INSTANCE.toTenantDetailVo(tenant);
    }

    /**
     * 超级管理员查看租户列表
     *
     * @param pageQueryTenantDto 租户分页Dto
     * @return List<GetTenantDetailVo /> 租户列表
     */
    @Override
    public ListRes<GetTenantDetailVo> getTenantDetails(PageQueryTenantDto pageQueryTenantDto) {
        LambdaQueryWrapper<Tenant> select = Wrappers.lambdaQuery();
        Optional<Integer> status = Optional.ofNullable(pageQueryTenantDto.getStatus());
        status.ifPresent(s -> select.eq(Tenant::getStatus, s));
        select.orderByDesc(Tenant::getCreatedTime);
        Page<Tenant> page = new Page<>(pageQueryTenantDto.getCurrent(), pageQueryTenantDto.getSize());
        Page<Tenant> tenantPage = tenantMapper.selectPage(page, select);
        List<GetTenantDetailVo> getTenantDetailVos = TenantConvert.INSTANCE.toGetTenantDetailVos(
            tenantPage.getRecords());
        return new ListRes<>(getTenantDetailVos, (int) tenantPage.getTotal());
    }

    /**
     * 根据企业信用代码更新租户状态
     *
     * @param updateTenantDto 更新租户状态
     * @return Integer 更新结果
     */
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, timeout = -1)
    public Integer updateTenantStatus(UpdateTenantDto updateTenantDto) {
        LambdaUpdateWrapper<Tenant> update = Wrappers.lambdaUpdate();
        LambdaQueryWrapper<Tenant> query = Wrappers.lambdaQuery();
        query.eq(Tenant::getNumber, updateTenantDto.getNumber());
        Tenant tenant = tenantMapper.selectOne(query);

        // 查询租户当前是什么状态，如果更改状态重复则抛出异常
        Integer nowStatus = getStatusByTenantId(updateTenantDto.getNumber());
        Assert.isFalse(nowStatus.equals(updateTenantDto.getStatus()), ErrorCode.STATUS_ERROR.getCode(),
                ErrorCode.STATUS_ERROR.getMessage());

        // 营业中状态不能修改为申请中状态
        Assert.isFalse(
                tenant.getStatus() == StatusConstant.ACTIVING && updateTenantDto.getStatus() == StatusConstant.APPLING,
                ErrorCode.STATUS_ERROR.getCode(), ErrorCode.STATUS_ERROR.getMessage());

        // 注销的状态不能修改为申请中状态
        Assert.isFalse(
                tenant.getStatus() == StatusConstant.CLOSED && updateTenantDto.getStatus() == StatusConstant.APPLING,
                ErrorCode.STATUS_ERROR.getCode(), ErrorCode.STATUS_ERROR.getMessage());

        // 判断如果是创建状态，就给租户创建数据库，并写入租户路由表
        if (updateTenantDto.getStatus() == StatusConstant.ACTIVING) {

            // 如果是其他状态，说明该租户已经创建过数据库
            Assert.isTrue(tenant.getStatus() == StatusConstant.APPLING, ErrorCode.DATABASE_REPEAT.getCode(),
                    ErrorCode.DATABASE_REPEAT.getMessage());
            // 如果租户还是没有数据库状态，就给其创建数据库
            // 租户数据库名称,从数据库查询到租户Id，拼接一个UUID
            String tenantSchema = "tenant_" + tenant.getTenantId();
            // 写租户路由表
            addTenantProperties(tenant.getDomain(), tenantSchema);
            // 更新configMap中的tenantMap
            configService.updateTenantMap(tenant.getDomain(),tenantSchema);
            // 执行版本迁移
            dataSourceConfiguration.migration(tenantSchema);
        }
        update.eq(Tenant::getNumber, updateTenantDto.getNumber()).set(Tenant::getStatus, updateTenantDto.getStatus());
        return tenantMapper.update(new Tenant(), update);
    }

    /**
     * 查询所有租户名字、域名、tenantId
     *
     * @return List<Tenant /> 租户列表
     */
    @Override
    public List<Tenant> getTenants() {
        LambdaQueryWrapper<Tenant> select = Wrappers.lambdaQuery();
        select.select(Tenant::getName, Tenant::getDomain, Tenant::getTenantId, Tenant::getNumber);
        return tenantMapper.selectList(select);
    }

    /**
     * 根据租户信用码查询租户状态
     *
     * @param number 租户信用码
     * @return 租户状态
     */
    @Override
    public Integer getStatusByTenantId(String number) {
        LambdaQueryWrapper<Tenant> query = Wrappers.lambdaQuery();
        query.eq(Tenant::getNumber, number);
        return tenantMapper.selectOne(query).getStatus();
    }

    /**
     * 写租户路由表
     *
     * @param tenantDomain 租户标识
     * @param tenantSchema 租户schema
     */
    private void addTenantProperties(String tenantDomain, String tenantSchema) {
        SetPropertyDto setPropertyDto = new SetPropertyDto();
        setPropertyDto.setKey(tenantRouteProperties.getKey() + tenantDomain);
        setPropertyDto.setValue(tenantSchema);
        setPropertyDto.setProfile(tenantRouteProperties.getProfile());
        propertyMapper.insertProperties(setPropertyDto);
    }

    /**
     * 迁移所有租户数据库
     *
     * @return 迁移信息
     */
    @Override
    public GetMigrationInformationVo migrationAllTenant() {
        String prefix = "tenant_";
        LambdaQueryWrapper<Tenant> query = Wrappers.lambdaQuery();
        query.select(Tenant::getTenantId).eq(Tenant::getStatus, StatusConstant.ACTIVING);
        List<Tenant> tenants = tenantMapper.selectList(query);
        List<String> schemas = tenants.stream()
            .map(tenant -> prefix + tenant.getTenantId())
            .collect(Collectors.toList());
        return FlywayConvert.INSTANCE.toGetMigrationInformationVo(dataSourceConfiguration.migration(schemas));
    }
}
